#include "YamlValidator.h"
#include "Utils.h"
#include "Lang.h"

YamlValidator::YamlValidator(FormatValidator v, YAML::Node n, unsigned int flags) : valid(false), flags(flags), format(v) {

    if(!v.isValid()) {
        return;
    }

    if(flags & syvCopyInput)
        values = copy(n);
    else
        values = n;

    valid = validate(n);
}

inline bool isLeaf(YAML::Node n) { return (n.IsMap() && n[SYV_KEYWORD_TYPE]); }

bool YamlValidator::containsRequiredFields(YAML::Node f) const {
    SYVDEBUGOUT(SYVTRACE,
    printf("containsRequiredFields(f)\n");
    printf("dump(f):\n");
    dump(f);)

    if(isLeaf(f)) {
        bool allowNull = format.getEffectiveAllowNull(f);
        throw ValidityException(syvNullNotAllowed, "allowNull is %s\n", allowNull?"TRUE":"FALSE");
        return !allowNull;
    } else {
        for(YAML::Node::const_iterator i = f.begin(); i != f.end(); i++) {
            if(containsRequiredFields(i->second)) return true;
        }
    }

    return false;
}

bool YamlValidator::validateSection(std::string where, YAML::Node f, YAML::Node n) const throw (ValidityException) {
    SYVDEBUGOUT(SYVTRACE,
    printf("ValidateSection(%s)\n", where.c_str());
    printf("f:\n");
    dump(f);
    printf("n:\n");
    dump(n);
    )

    if(isLeaf(f)) {
        bool allowNull = format.getEffectiveAllowNull(f);
        if(!n) {
            if(allowNull) return true;
            throw ValidityException(syvKeyMissing, "Key \"%s\" is missing.\n", where.c_str());
            bool b = format.satisfiesTypeRequirements(f[SYV_KEYWORD_TYPE].Scalar(), n, f);
            return b;
        } else {
            if(n.IsNull()) {
                return allowNull;
            } else {
                return true;
            }
        }
    } else {
        for(YAML::Node::const_iterator i = f.begin(); i != f.end(); i++) {
            std::string key = i->first.Scalar();
            YAML::Node v = n[key];
            if(!validateSection(where + SYV_VARIABLE_PATH_SEPARATOR + key, i->second, v)) {
                throw ValidityException(syvUnknownError, "Section %s contains error(s)\n", (where + SYV_VARIABLE_PATH_SEPARATOR + key).c_str());
                return false;
            }
        }
        if(flags & syvUndefinedKeysIllegal) {
            for(YAML::Node::const_iterator i = n.begin(); i != n.end(); i++) {
                if(!f[i->first.Scalar()]) {
                    throw ValidityException(syvKeyUndefined, "Unexpected key \"%s\" found.\n", i->first.Scalar().c_str());
                    return false;
                }
            }
        }
    }
    return true;
}

YAML::Node YamlValidator::getBaseVarDef(std::string path, std::string&left) const { // TODO: move to FormatValidator
    if(!valid) return YAML::Node(YAML::NodeType::Undefined);

    std::string s = path;
    YAML::Node basevardef;
    while(1 && s.length()) {
        YAML::Node t = getNode(format.format, s);
        if(t) { basevardef = t; break; }
        std::string::size_type p = s.find_last_of(SYV_VARIABLE_PATH_SEPARATOR);
        if(p == std::string::npos) break;
        s = s.substr(0, p);
    }

    if(!basevardef) return basevardef;

    unsigned sz = s.length();
    left = path.substr(sz, path.length() - sz);

    return basevardef;
}

YAML::Node YamlValidator::findTypeDefinitionOfVariableByPath(std::string path) const { //TODO: move to FormatValidator.

    if(!valid) return YAML::Node(YAML::NodeType::Undefined);

    std::string left;
    YAML::Node basevardef = getBaseVarDef(path, left);
    
    YAML::Node vardef = format.getType(basevardef[SYV_KEYWORD_TYPE].Scalar() + left);
    return vardef;
}

YAML::Node YamlValidator::getDefaultValueOfSection(YAML::Node def) const throw (ValidityException) {
    YAML::Node n;
    if(isLeaf(def)) {
        n = format.getDefault(def);
    } else {
        if(def.IsMap()) {
            for(YAML::Node::const_iterator i = def.begin(); i != def.end(); i++) {
                n[i->first.Scalar()] = getDefaultValueOfSection(i->second);
            }
        } else { // TODO: this happens when root section is missing.
            throw ValidityException(syvInternalError, "Internal error. def:\n"); 
//            dump(def);
        }
    }
    return n;
}

YAML::Node YamlValidator::getValue(std::string path) const throw (ValidityException) {
    SYVDEBUGOUT(SYVTRACE,
    printf("getValue('%s')\n", path.c_str());
    )

    if(!valid) return YAML::Node(YAML::NodeType::Undefined);
    YAML::Node v = getNode(values, path);
    //TODO: if(!v) // XXX: should never happen if validated, if can't be null then isn't validated, if can be null will have no effect in overrideValues.
    YAML::Node n;
 
    std::string left;
    YAML::Node def = getBaseVarDef(path, left);
    if(!def) { throw ValidityException(syvNoDefinition, "No definition.\n"); return YAML::Node(YAML::NodeType::Undefined); }

    if(left[0] == SYV_VARIABLE_PATH_SEPARATOR) left = left.substr(1, left.length() - 1);
    YAML::Node defaultValues = getDefaultValueOfSection(def);
    if(left.length()) {
        n = getNode(defaultValues, left);
    } else {
        n = defaultValues;
    }
    return overrideValues(n, v);
}


bool YamlValidator::validate(YAML::Node n) const throw (ValidityException) {

    for(YAML::Node::const_iterator i = format.format.begin(); i != format.format.end(); i++) {
    
        std::string key = i->first.Scalar();
        if(!key.compare(SYV_KEYWORD_TYPEDEFINITIONS)) { continue; }
        if(!validateSection(key, i->second, n[key])) {
            throw ValidityException(syvUnknownError, "Section %s contains error(s)\n", key.c_str());
            return false;
        }
    
    }
    
    return true;
}
